Add field-reference-to-alias conversion so stored reference_to serves as reference - #13847
Conversation
…s reference Protocol-18 ADR-0087 D2 entry canonicalizing the legacy objectql field-key dialect reference_to -> reference on object and object-extension fields, retiredFromLoadPath from day one (FieldSchema keeps its named rejection). Registered in the step-18 migration chain so os migrate meta (and --stored) rewrites old sources; serve-face end-to-end pins in metadata-protocol prove a stored row seeded around the Zod gate is served with only the canonical spelling across getMetaItems / getMetaItem / loadMetaFromDb. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
📓 Docs Drift CheckThis PR changes 1 package(s): 34 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e5e45e1399a436b9ad57e9fee31a52628a730f93 && git checkout e5e45e1399a436b9ad57e9fee31a52628a730f93
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6193e576d8f243e42d3ce8f15fa91b0763d49aaf 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0 && git checkout -B drift-repro 6193e576d8f243e42d3ce8f15fa91b0763d49aaf && git merge --no-ff 4e3cdf2bb5facad1441ffd55fccc0fcbe4e172f0
node scripts/docs-audit/affected-docs.mjs --json 6193e576d8f243e42d3ce8f15fa91b0763d49aaf
|
…ledger at its frozen 63 The three new makeStubEngine([legacyReferenceToRow]) call sites each raised TS2322 under the debt instrument (the parameter intersection collapses metadata to string, while stored-seam seeds deliberately carry object-literal metadata that the stub stringifies). Annotating the fixture const `any` — the file's established stub spelling — removes exactly the 3 new errors; the 7 pre-existing frozen errors in this file are untouched, so the package measures 63 again, matching the ledger. No behavior or assertion change; suite 9/9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
|
os-dev-report {
"issue": "PR#13847-patch",
"status": "done",
"branch": "claude/issue-13700-reference-to-conversion",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13847",
"premise_still_valid": true,
"summary": "Attribution held exactly: the +3 raw tsc errors were TS2322 at protocol.stored-conversions.test.ts (171,44), (182,44), (194,56) — the three makeStubEngine([legacyReferenceToRow]) call sites in the new #13700 describe block, each reading (placeholder spelling for the sanitizer, angle brackets removed): Type '(type: string; name: string; metadata: (object literal))' is not assignable to type 'Partial(Row) intersect (type: string; name: string; metadata: unknown)'. Root cause: the intersection collapses the seed parameter's metadata to string, while stored-seam seeds deliberately carry object-literal metadata that makeStubEngine stringifies; the neighboring #3903 fixtures err identically and are frozen inside the 63, so the file's non-erroring paradigm is its established stub spelling (const engine: any, res: any, as-any casts), not the neighboring fixtures themselves. Fix: one type annotation — const legacyReferenceToRow: any — plus a 3-line comment; zero behavior or assertion change, ledger untouched, package back at exactly its frozen 63 with the file's 7 pre-existing frozen errors intact (no surplus drift). Pushed as plain append commit 4e3cdf2bb; auto-merge left armed, draft bit and labels untouched.",
"tests": "Instrument reproduced per the gate's own measureDebt: workspace tsc, --noEmit --pretty false -p packages/metadata-protocol/tsconfig.json from repo root, closure built first (pnpm --filter closure build, exit 0), NODE_OPTIONS heap 4096 matching the gate's pinned CI ceiling. Before fix: 66 error lines counted by the gate's own TSC_ERROR_LINE anchor, 10 in this file. After fix, re-run on the final commit 4e3cdf2bb: 63 exactly (= frozen DEBT entry), 7 in this file (the pre-existing frozen ones). vitest run protocol.stored-conversions.test.ts: 'Test Files 1 passed (1) / Tests 9 passed (9)'. check:type-check-coverage --self-test: '47 semantic case(s) + 65 observation case(s) + 43 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold', exit 0. Full --re-measure NOT locally runnable: it refuses on this worktree ('43 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk') and demands the full turbo packages build, which cold cannot fit the ~10-minute foreground cap — declared narrowing per dispatch fallback; the gate's static half did run and printed 'check-type-check-coverage: OK — 66/78 workspace packages type-checked'. Also green at head: check:nul-bytes (7599 files, exit 0), and the kind-derived families a test edit moves per dispatch-gates.mjs run without paths (stale-tree warning noted, 15 commits behind origin/main): check:query-options-erasure ('at the ceiling... no files added', exit 0), check:engine-double-contract ('667 (file, verb) row(s) held by the RETAINED ledger', exit 0), check:cross-package-test-inputs ('All 117 self-test cases passed... OK: 24 package(s)', exit 0). CI's own re-measure job on the pushed head is the authoritative rerun; awaiting it is the PM's half.",
"mcp_calls": "1 — this report comment",
"open_questions": [],
"out_of_scope_findings": []
}Generated by Claude Code Generated by Claude Code |
Fixes #13700
What landed (work item 2 — the page-header-subtitle-alias precedent, adapted to a key FieldSchema already rejects)
field-reference-to-alias(packages/spec/src/conversions/registry.ts, protocol-18 bucket): field keyreference_torenames toreferenceonobjects[].fieldsandobjectExtensions[].fields, house ADR-0087 别名转换会把「被遮蔽的旧拼法」留在存量元数据里 —— 节点 config 收紧后它从静默丢弃变成执行期硬拒 #4923 precedence (an already-canonicalreferencewins; a redundant twin is dropped; a disagreeing pair is kept for the author).retiredFromLoadPath: truefrom day one — the key never had an accepted authoring window (FieldSchemahas always refused it by name), so the authoring-surface rejection with its rename hint is UNCHANGED. This is the precedent-implied relation to the existing rejection surface: normalization owns data at rest, the rejection keeps teaching authors; nothing was loosened at parse time. The widened accept surface is exactly the stored-rehydration pass and the migration chain.os migrate meta: registered in step 18conversionIdsplus rationale prose (packages/spec/src/migrations/registry.ts). No CLI code change was needed —migrate metareplays the chain viaapplyMetaMigrations, so chain registration IS the CLI support (the claim comment predicted possiblepackages/clitouches; they did not materialize).packages/rest/src/rest-server.tsuntouched (cross-seat fence respected).conversions.test.ts: notice shape, ADR-0087 别名转换会把「被遮蔽的旧拼法」留在存量元数据里 —— 节点 config 收紧后它从静默丢弃变成执行期硬拒 #4923 precedence, idempotence, load-posture exclusion (the plain posture does NOT rewrite — the schema rejection stays the only authoring answer), stored-row rehydration, and the premise pin thatFieldSchemastill declaresreferenceand rejectsreference_toby name.@objectstack/specminor.gen:spec-changesandgen:upgrade-guidewere run and produced byte-identical output — both artifacts project released majors only (protocol 17), and all 14 sibling protocol-18 conversions are equally absent from them today.check:spec-changes,check:upgrade-guide,check:migration-registry,check:adr-0087-registrationall green.Serve-face evidence (the credential ui#6837 half 2 waits on)
End-to-end pins in
packages/metadata-protocol/src/protocol.stored-conversions.test.ts: a stored object row carryingreference_tois seeded directly into the stub engine — bypassingsaveMetaItem's schema gate, exactly like a real legacy row — and served through the three real serve seams:getMetaItems: the served field carriesreference: 'crm_company'and the dialect key is ABSENT;getMetaItem: same, plus_diagnostics.valid: true(chain-owned history is not reported broken);loadMetaFromDb: boot hydration registers the CONVERTED body,invalid: 0.The serve log shows the live notice verbatim: "stored object/crm_contact carries a pre-protocol shape; converted field.reference_to at objects[0].fields.company_id.reference ... re-save it (Studio edit or save, or run
os migrate meta --stored --apply) to persist the canonical shape".Ablation — both legs rebuilt through dist (metadata-protocol resolves
@objectstack/specvia its exports, i.e.dist/, per the KNOWN_UNALIASED_TEST_IMPORTS ledger). Predicted direction, stated before the run: the 3 new pins go red, the 6 pre-existing ones stay green. Mutation leg: the conversion's two apply-call key arguments were replaced with a marker; the mutation was proven on disk by anchored grep counts (marker count 0 to 2, original callsite count 2 to 0), spec was rebuilt, the marker was proven present in dist viaablation-dist-preflight(plant mode), and the serve pin then failed exactly as predicted: 3 failed, 6 passed. Restore leg:git checkout HEADrestore proven byte-identical to the HEAD blob (git hash-objectequal,git diff HEADempty), spec rebuilt again, marker proven ABSENT from all 215 built dist files (preflight--absent), serve pin 9/9 green. The mutation script carried an EXIT/INT/TERM restore trap using absolute paths.Work item 1 — not re-measured
Per triage comment 5479038256 it is already answered (0
reference_tohits in conversions+migrations with an 89-hit control on the same file). The premise was re-verified once at pickup against origin/main (grep exit 1 on those paths; positive controlpage-header-subtitle-aliaspresent in registry, walk and tests): still uncovered until this PR — no race with a parallel landing.Work item 3 — census only, measured and NOT judged (per the dispatch fence, submitted for the spec-seat ruling)
Key readings (full table in the report comment on #13700):
reference_fieldhas ZERO occurrences in the whole objectstack tree (positive controlreference_to: 212 hits, same query shape);referenceFieldin objectstack is 14 hits, every one either the prose placeholder pattern "bracket referenceField colon parentId" or the unrelated identifierreferenceFieldNamesin packages/rest. In objectui (read-only, at origin/main a7807b4) the two spellings are TWO DIFFERENT CONCEPTS, both declared in objectui's OWN types package:reference_field= display field of the referenced object (39 hits, declared + read + emitted + taught in docs),referenceField= child-side FK column of a related list (105 hits, declared + heavily read). Neither spelling is a spec key today. Nothing was written into the conversion table.Verification (union at head ef1563e)
typecheckgreen including the test layer (check:test-typecheck OK).protocol.stored-conversions.test.ts9/9. Note: the package declares notypecheckscript — its type coverage rides the repo-level CI programs; NOT MEASURED locally.scripts/pm/dispatch-gates.mjsat ef1563e (stderr header names this tree/commit); the directly-implicated families ran green locally: check:adr-0087-registration, check:spec-changes, check:upgrade-guide, check:migration-registry, check:authorable-surface, check:engine-double-contract, check:cross-package-test-inputs, check:test-source-alias, check:query-options-erasure, check:objectql-double-limit, check:where-matcher, check:nul-bytes. The full farm is CI's run.Contract review
Clause-②: yes — the conversion makes a previously-rejected spelling accepted-and-normalized on the stored/migrate paths. Parked as draft with
needs:contract-review(dual carrier: this PR and card #13700), per the in-seat contract-review flow (#13795). ui#6837 remains open and is not addressed here — that card is half 2 and stays blocked on this one.Generated by Claude Code
Generated by Claude Code